package edu.ntu.cloudroid.xacml.function;

import java.util.List;


import com.sun.xacml.EvaluationCtx;
import com.sun.xacml.attr.AttributeValue;
import com.sun.xacml.attr.BooleanAttribute;
import com.sun.xacml.attr.DoubleAttribute;
import com.sun.xacml.cond.EvaluationResult;
import com.sun.xacml.cond.FunctionBase;

import edu.ntu.cloudroid.model.IDs;
import edu.ntu.cloudroid.model.data.LocationData;
import edu.ntu.cloudroid.model.profile.ProfileData;
import edu.ntu.cloudroid.server.backendservice.DataService;
import edu.ntu.cloudroid.server.backendservice.ProfileService;
import edu.ntu.cloudroid.xacml.Utilities;
import edu.ntu.cloudroid.xacml.XACMLException;
import edu.ntu.cloudroid.xacml.XACMLStrings;

public class SubjectWithinRadiusOfResource extends FunctionBase
{
   private static final double EARTH_RADIUS = 6371.0;

   // the parameter types, in order, and whether or not they're bags
   private static final String params[] = { DoubleAttribute.identifier };
   private static final boolean bagParams[] = { false };

   public SubjectWithinRadiusOfResource()
   {
      // use the constructor that handles mixed argument types
      super(XACMLStrings.FN_SubjectWithinRadiusOfResource, 0, params,
            bagParams, BooleanAttribute.identifier, false);
   }

   @SuppressWarnings("rawtypes")
   @Override
   public EvaluationResult evaluate(List inputs, EvaluationCtx context)
   {
      AttributeValue[] argValues = new AttributeValue[inputs.size()];
      EvaluationResult result = evalArgs(inputs, context, argValues);
      if (result != null)
         return result;

      try {
         double radius = ((DoubleAttribute) (argValues[0])).getValue();
         double currentLatitude = ((DoubleAttribute) Utilities
               .getSubjectAttributeValue(XACMLStrings.ATTR_Latitude,
                     DoubleAttribute.identifier, context)).getValue();
         double currentLongitude = ((DoubleAttribute) Utilities
               .getSubjectAttributeValue(XACMLStrings.ATTR_Longitude,
                     DoubleAttribute.identifier, context)).getValue();

         boolean evalResult = false;

         // if radius is 0, disallow access
         if (radius == 0) {
            return EvaluationResult.getInstance(false);
         }

         ProfileData resource = ProfileService.getProfile(Utilities.getResourcePersonKey(context));
         LocationData latest = (LocationData)DataService.getLatestData(resource, IDs.DataID.LocationData.ordinal());
         
//         LocationStamp latest = DataService.getLatestLocationStamp(Utilities
//               .getResourcePersonKey(context));

         // Calculate 'haversine' distance between two points
         double dLat = Math.toRadians((latest.getLatitude() - currentLatitude));
         double dLong = Math
               .toRadians((latest.getLongitude() - currentLongitude));
         double targetLatitudeRad = Math.toRadians(currentLatitude);
         double subjectLatitudeRad = Math.toRadians(latest.getLatitude());

         double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
               + Math.sin(dLong / 2) * Math.sin(dLong / 2)
               * Math.cos(targetLatitudeRad) * Math.cos(subjectLatitudeRad);
         double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
         double d = EARTH_RADIUS * c;

         if (d <= radius) {
            evalResult = true;
         }

         return EvaluationResult.getInstance(evalResult);
      } catch (Exception e) {
         return makeProcessingError("Error in parsing attribute: "
               + e.getMessage());
      }
   }
}
